// Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------
// Modification history:
// feature: content filter topic
// ------------------------------------------------------------------

#include "DDSFilterValue.hpp"

#include <cassert>
#include <cstring>
#include <string>
#include <regex>
#include <unordered_set>
#include <elog/Log.hpp>
#include <elog/ExceptionCode.hpp>
#include <elog/ReturnCode.hpp>

namespace evbs {
namespace ertps {
namespace rtps {
namespace DDSSQLFilter {

template <typename T>
int compare_values(T lvalue, T rvalue) {
    return lvalue < rvalue ? -1 : lvalue > rvalue ? 1 : 0;
}

/**
 * Check if a value is negative.
 * Only used during promotion to UNSIGNED_INTEGER.
 */
static bool is_negative(const DDSFilterValue& value) {
    switch (value.kind) {
        case DDSFilterValue::ValueKind::BOOLEAN:
            return false;

        case DDSFilterValue::ValueKind::ENUM:
        case DDSFilterValue::ValueKind::SIGNED_INTEGER:
            return value.signed_integer_value < 0;

        // The rest of the types shall never be promoted to UNSIGNED_INTEGER
        default:
            assert(false);
    }

    return false;
}

/**
 * Performs promotion to SIGNED_INTEGER
 */
static int64_t to_signed_integer(const DDSFilterValue& value) {
    switch (value.kind) {
        case DDSFilterValue::ValueKind::BOOLEAN:
            return value.boolean_value ? 1 : 0;

        case DDSFilterValue::ValueKind::ENUM:
            return value.signed_integer_value;

        // The rest of the types shall never be promoted to SIGNED_INTEGER
        default:
            assert(false);
    }

    return 0;
}

/**
 * Performs promotion to UNSIGNED_INTEGER
 */
static uint64_t to_unsigned_integer(const DDSFilterValue& value) {
    switch (value.kind) {
        case DDSFilterValue::ValueKind::BOOLEAN:
            return value.boolean_value ? 1 : 0;

        case DDSFilterValue::ValueKind::ENUM:
        case DDSFilterValue::ValueKind::SIGNED_INTEGER:
            return static_cast<uint64_t>(value.signed_integer_value);

        // The rest of the types shall never be promoted to UNSIGNED_INTEGER
        default:
            assert(false);
    }

    return 0;
}

/**
 * Performs promotion to FLOAT
 */
static long double to_float(const DDSFilterValue& value) {
    switch (value.kind) {
        case DDSFilterValue::ValueKind::ENUM:
        case DDSFilterValue::ValueKind::SIGNED_INTEGER:
            return static_cast<long double>(value.signed_integer_value);

        case DDSFilterValue::ValueKind::UNSIGNED_INTEGER:
            return static_cast<long double>(value.unsigned_integer_value);

        case DDSFilterValue::ValueKind::FLOAT_CONST:
        case DDSFilterValue::ValueKind::FLOAT_FIELD:
        case DDSFilterValue::ValueKind::DOUBLE_FIELD:
        case DDSFilterValue::ValueKind::LONG_DOUBLE_FIELD:
            return value.float_value;

        // The rest of the types shall never be promoted to FLOAT
        default:
            assert(false);
    }

    return 0;
}

/**
 * Performs promotion to STRING
 */
static void to_string_value(const DDSFilterValue& in, vbsutil::xmlparser::string_255& out) {
    assert(DDSFilterValue::ValueKind::CHAR == in.kind);
    out.assign(&in.char_value, 1);
}

static std::string transform_from_like_expression_to_regex(const std::string& expr) {
    static std::unordered_set<char> special_chars_in_regex = {
        '.', '*', '+', '?', '(', ')', '[', ']', '{', '}', '^', '$', '|'
    };
    std::string res;
    const int n = expr.size();
    for (int i = 0; i < n; ++i) {
        if (special_chars_in_regex.find(expr[i]) != special_chars_in_regex.end()) {
            res += '\\';  // Escape the special character
            res += expr[i];
        } else if (expr[i] == '%') {
            res += ".*";  // Replace % with .*
        } else if (expr[i] == '_') {
            res += '.';  // Replace _ with .
        } else if (expr[i] == '\\') {
            if (i + 1 < n) { // has next character
                ++i;
                if (special_chars_in_regex.count(expr[i]) || expr[i] == '\\') {
                    res += '\\';
                }
                res += expr[i];
            }
        } else {
            res += expr[i];  // Keep the character as is
        }
    }
    return res;
}

void DDSFilterValue::copy_from(const DDSFilterValue& other, bool copy_regular_expression) noexcept {
    kind = other.kind;
    switch (kind) {
        case ValueKind::BOOLEAN:
            boolean_value = other.boolean_value;
            break;

        case ValueKind::CHAR:
            char_value = other.char_value;
            break;

        case ValueKind::ENUM:
        case ValueKind::SIGNED_INTEGER:
            signed_integer_value = other.signed_integer_value;
            break;

        case ValueKind::UNSIGNED_INTEGER:
            unsigned_integer_value = other.unsigned_integer_value;
            break;

        case ValueKind::FLOAT_CONST:
        case ValueKind::FLOAT_FIELD:
        case ValueKind::DOUBLE_FIELD:
        case ValueKind::LONG_DOUBLE_FIELD:
            float_value = other.float_value;
            break;

        case ValueKind::STRING:
            string_value = other.string_value;
            break;

        default:
            assert(false);
    }

    if (copy_regular_expression) {
        regular_expr_kind_ = other.regular_expr_kind_;
        if (has_value()) {
            try {
                value_has_changed();
            } catch (const std::regex_error& e) {
                elogError(DDSSQLFILTER, vbsutil::elog::ReturnCode_t::RETCODE_ERROR,
                          "Regular expression error in copy_from: " << e.what());
            }
        }
    }
}

int DDSFilterValue::compare(const DDSFilterValue& lhs, const DDSFilterValue& rhs) noexcept {
    if (lhs.kind == rhs.kind) {
        switch (lhs.kind) {
            case ValueKind::BOOLEAN:
                return lhs.boolean_value - rhs.boolean_value;

            case ValueKind::CHAR:
                return lhs.char_value - rhs.char_value;

            case ValueKind::SIGNED_INTEGER:
            case ValueKind::ENUM:
                return compare_values(lhs.signed_integer_value, rhs.signed_integer_value);

            case ValueKind::UNSIGNED_INTEGER:
                return compare_values(lhs.unsigned_integer_value, rhs.unsigned_integer_value);

            case ValueKind::FLOAT_FIELD:
                return compare_values(static_cast<float>(lhs.float_value), static_cast<float>(rhs.float_value));

            case ValueKind::DOUBLE_FIELD:
                return compare_values(static_cast<double>(lhs.float_value), static_cast<double>(rhs.float_value));

            case ValueKind::LONG_DOUBLE_FIELD:
                return compare_values(lhs.float_value, rhs.float_value);

            case ValueKind::STRING:
                return std::strcmp(lhs.string_value.c_str(), rhs.string_value.c_str());

            // As the grammar does not allow constant vs constant comparisons, FLOAT_CONST should never reach here
            case ValueKind::FLOAT_CONST:
            default:
                assert(false);
        }
    } else if (lhs.kind < rhs.kind) {
        // We want to always promote rhs
        return -compare(rhs, lhs);
    } else {
        /* Type promotion rules are enforced during the construction of the expression tree on
         * DDSFilterFactory. For the types on which promotion is supported, a corresponding to_xxx method exists
         * that will assert if an invalid promotion is performed.
         */
        switch (lhs.kind) {
            case ValueKind::ENUM:
            case ValueKind::SIGNED_INTEGER: {
                return compare_values(lhs.signed_integer_value, to_signed_integer(rhs));
            }

            case ValueKind::UNSIGNED_INTEGER:
                return is_negative(rhs) ? 1 : compare_values(lhs.unsigned_integer_value, to_unsigned_integer(rhs));

            case ValueKind::FLOAT_CONST:
            case ValueKind::FLOAT_FIELD:
                return compare_values(static_cast<float>(lhs.float_value), static_cast<float>(to_float(rhs)));

            case ValueKind::DOUBLE_FIELD:
                return compare_values(static_cast<double>(lhs.float_value), static_cast<double>(to_float(rhs)));

            case ValueKind::LONG_DOUBLE_FIELD:
                return compare_values(lhs.float_value, to_float(rhs));

            case ValueKind::STRING: {
                vbsutil::xmlparser::string_255 rvalue;
                to_string_value(rhs, rvalue);
                return std::strcmp(lhs.string_value.c_str(), rvalue.c_str());
            }

            // Promotion to boolean or char are not allowed.
            case ValueKind::BOOLEAN:
            case ValueKind::CHAR:
            default:
                assert(false);
                break;
        }
    }

    return 0;
}

void DDSFilterValue::as_regular_expression(bool is_like_operand) {
    regular_expr_kind_ = is_like_operand ? RegExpKind::LIKE : RegExpKind::MATCH;
    if (has_value()) {
        value_has_changed();
    }
}

void DDSFilterValue::value_has_changed() {
    if (RegExpKind::NONE != regular_expr_kind_) {
        std::string expr;

        switch (kind) {
            case ValueKind::CHAR:
                expr = char_value;
                break;

            case ValueKind::STRING:
                expr = string_value.c_str();
                break;

            default:
                assert(false);
        }

        if (RegExpKind::LIKE == regular_expr_kind_) {
            expr = transform_from_like_expression_to_regex(expr);
        }

        regular_expr_.reset(new std::regex(expr));
    }
}

bool DDSFilterValue::is_like(const DDSFilterValue& other) const noexcept {
    assert(other.regular_expr_);

    vbsutil::xmlparser::string_255 char_string_value;

    switch (kind) {
        case ValueKind::CHAR:
            assert(ValueKind::STRING == other.kind);
            to_string_value(*this, char_string_value);
            return std::regex_match(char_string_value.c_str(), *other.regular_expr_);

        case ValueKind::STRING:
            switch (other.kind) {
                case ValueKind::CHAR:
                case ValueKind::STRING:
                    return std::regex_match(string_value.c_str(), *other.regular_expr_);

                default:
                    assert(false);
            }
            break;

        default:
            assert(false);
    }

    return false;
}

}  // namespace DDSSQLFilter
}  // namespace rtps
}  // namespace ertps
}  // namespace evbs
